package behavior;

/**
 * Creates the BoidBehavior object which updates a boid's behavior. It acts
 * as a model for a boid, updating its new position and new velocity.  
 * 
 * @author Ga-Young Joung and Perry Zheng
 */
import java.awt.Color;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import effects.Sound;

import utilities.Vector;
import individual.*;

public class BoidBehavior extends Behavior{

	final static int PERCEIVED_CENTER_SCALE = 200;
	final static int REPULSION_VELOCITY_SCALE = 10;
	final static int PERCEIVED_VELOCITY_SCALE = 200;
	final static String SOUND_FILENAME = "effects/laser.wav";
	
	private Parameters myParameters;
	private String myAttractingClassName;
	private String myRepulsingClassName;
	private int myAttractionScale;
	private int myRepulsingScale;
	private Random myGenerator;
	private boolean myKillable;
	
	/**
	 * Constructs a boid behavior object. 
	 * @param attractingClassName the name of the class the boid is attracted to
	 * @param attractionScale the attraction constant
	 * @param repulsingClassName  the name of the class the boid is repulsed from
	 * @param repulsionScale the repulsion constant
	 * @param parameters the parameters such as radius and min radius
	 * @param killable whether the boid is killable or not
	 */
	public BoidBehavior(String attractingClassName, int attractionScale, String repulsingClassName, int repulsionScale,
			Parameters parameters, boolean killable) {
		myAttractingClassName = attractingClassName;
		myRepulsingClassName = repulsingClassName;
		myAttractionScale = attractionScale;
		myRepulsingScale = repulsionScale;
		myParameters = parameters;
		myGenerator = new Random();
		myKillable = killable;
	}
	
	/**
	 * The main model for updating a moving entity's velocity. Returns the
	 * new velocity based on the current entity's position and its neighbors
	 */
	public Vector getNextVelocity(MovingEntity current, List<MovingEntity> allMovingEntities) {
		Boid bcurrent = (Boid) current;
		
		List<Boid> allBoids = (List<Boid>) getListOfType("individual.Boid", allMovingEntities);
		
		ArrayList<Boid> neighbors = new ArrayList<Boid>();
    	ArrayList<Boid> tooCloseNeighbors = new ArrayList<Boid>();
    	ArrayList<Boid> repulsingNeighbors = new ArrayList<Boid>();
    	ArrayList<Boid> attractingNeighbors = new ArrayList<Boid>();
    	
    	for (Boid pNeighbor : allBoids){
    		if (!this.equals(pNeighbor)){
	    		double distance = distance(bcurrent.getCenter(), pNeighbor.getCenter());
	    		if (distance < myParameters.myMinDistance) {
	    			tooCloseNeighbors.add(pNeighbor);
	    		}
	    		else if (distance < myParameters.myRadius){
	    			if (current.getClass().isInstance(pNeighbor)) {
	    				neighbors.add(pNeighbor);
	    			} else {
	    				Class classType;
	    				if (!myAttractingClassName.equals("None")) {
	    					try {
								classType = Class.forName("individual."+myAttractingClassName);
		    					if (classType.isInstance(pNeighbor)) {
		    						attractingNeighbors.add(pNeighbor);
		    					}
							} catch (ClassNotFoundException e) {
								System.err.println(e);
							}
	    				}
	    				if (!myRepulsingClassName.equals("None")) {
	    					try {
	    						classType = Class.forName("individual." +myRepulsingClassName);
	    						if (classType.isInstance(pNeighbor)) {
	    							repulsingNeighbors.add(pNeighbor);
	    						}
	    					} catch (ClassNotFoundException e) {
								System.err.println(e);
							}
	    				}
	    			}
	    		} 
    		}
    	}
    	if (myKillable) {
    		if (repulsingNeighbors.size() > 15) {
    			current.setAsDead();
    			System.out.println("one down!");
            	Sound s = new Sound(SOUND_FILENAME);
            	s.play();
    		}
    	}
    	
    	Vector perceivedCenter = getPerceivedCenter(bcurrent.getCenter(), neighbors, PERCEIVED_CENTER_SCALE);
    	Vector repulsionVelocity = getRepulsionVelocity(bcurrent.getCenter(), tooCloseNeighbors, REPULSION_VELOCITY_SCALE);
    	Vector perceivedVelocity = getPerceivedVelocity(bcurrent.getVelocity(), neighbors, PERCEIVED_VELOCITY_SCALE);
    	Vector attractionVelocityFromNeighbors = getAttractionVelocity(bcurrent.getCenter(), attractingNeighbors, myAttractionScale);
    	Vector repulsingVelocityFromNeighbors = getRepulsionVelocity(bcurrent.getCenter(), repulsingNeighbors, myRepulsingScale);    	
    	
    	Vector post = perceivedVelocity;
        post = post.add(perceivedCenter.add(repulsionVelocity));
        
        Vector temp = post.add(bcurrent.getVelocity());
        temp = temp.add(attractionVelocityFromNeighbors).add(repulsingVelocityFromNeighbors);
    	Vector v = new Vector((int)temp.x, (int)temp.y);
    	if(v.isZero()) {
    		bcurrent.getVelocity().perturb();
    		return this.getNextVelocity(current,allMovingEntities);
    	}
        return v;
	}
	
	/**
	 * Return the perceived center from the perspective of its neighbors    
	 * @param myCurrentCenter the center of the current boid
	 * @param neighbors all the neighbors
	 * @return the perceived center from the perspective of its neighbors
	 * @param scale the smaller, the greater the effect)
	 */
	public Vector getPerceivedCenter(Vector myCurrentCenter, List<Boid> neighbors, int scale) {
	   	Vector p = new Vector();
	   	if (neighbors.size()!=0) {
	    	for (Boid nei: neighbors) {
	    		p = p.add(nei.getCenter());
	    	}
	    	p = p.divide(neighbors.size());
	    	p = p.subtract(myCurrentCenter);
	    	p = p.divide(scale);
	   	}
	   	return p;
	}
	
	/**
	 * Get the repulsion velocity from its neighbors
	 * @param myCurrentCenter the center of the current boid
	 * @param tooCloseNeighbors a list of its neighbors
	 * @param scale the repulsion scale (the smaller, the greater the effect)
	 * @return repulsion velocity from its too close neighbors
	 */
	public Vector getRepulsionVelocity(Vector myCurrentCenter, List<Boid> tooCloseNeighbors, int scale) {
		Vector p = getAttractionVelocity(myCurrentCenter, tooCloseNeighbors, scale);
		return p.multiply(-1);
	}

	/**
	 * Get the attraction velocity from its neighbors
	 * @param myCurrentCenter the center of the current boid
	 * @param tooCloseNeighbors a list of its neighbors
	 * @param scale the attraction scale (the smaller, the greater the effect)
	 * @return attractiono velocity from its neighbors
	 */
	public Vector getAttractionVelocity(Vector myCurrentCenter, List<Boid> tooCloseNeighbors, int scale) {
		Vector p = new Vector();
		if (tooCloseNeighbors.size()!=0) {
			for (Boid nei : tooCloseNeighbors) {
				p = p.add(nei.getCenter().subtract(myCurrentCenter));
	        }
	        return p.divide(scale);
		}
		return p;	
	}
		
	/**
	 * Get the velocity of the current boid from the perspective of its neighbors
	 * @param myCurrentVelocity the current velocity
	 * @param neighbors the list of its neighbors
	 * @return velocity of the current boid from the perspective of its neighbors
	 *  @param scale (the smaller, the greater the effect)
	 */
	public Vector getPerceivedVelocity(Vector myCurrentVelocity, List<Boid> neighbors, int scale) {
		Vector p = new Vector();
	    //average velocity
	    if (neighbors.size()!=0) {
		    for (Boid nei : neighbors) {
		    	p = p.add(nei.getVelocity());
		    }
		    p = p.divide(neighbors.size());
		    p = p.subtract(myCurrentVelocity);
		    p = p.divide(scale);
		    
	    }
	    return p;
	}
		
	/**
	 * returns the distance between two vectors
	 * @param p1 
	 * @param p2
	 * @return the distance between two vectors
	 */
	public double distance(Vector p1, Vector p2) {
		return Math.sqrt((p1.x-p2.x)*(p1.x-p2.x)+(p1.y - p2.y)*(p1.y-p2.y));
	}
}